/****************************************************************************
Copyright (c) 2010-2012 cocos2d-x.org
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md).

https://axmolengine.github.io/

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/

#include "platform/Image.h"
#include "renderer/backend/PixelFormatUtils.h"

#include <string>
#include <ctype.h>

#include "base/axstd.h"
#include "base/Config.h"  // AX_USE_JPEG, AX_USE_WEBP

#define STBI_NO_JPEG
#define STBI_NO_PNG
#define STBI_NO_GIF
#define STBI_NO_PSD
#define STBI_NO_PIC
#define STBI_NO_PNM
#define STBI_NO_HDR
#define STBI_NO_TGA
#define STB_IMAGE_IMPLEMENTATION
#if AX_TARGET_PLATFORM == AX_PLATFORM_IOS
#    define STBI_NO_THREAD_LOCALS
#endif
#include "stb/stb_image.h"

extern "C" {
// To resolve link error when building 32bits with Xcode 6.
// More information please refer to the discussion in https://github.com/cocos2d/cocos2d-x/pull/6986
#if defined(__unix) && (AX_TARGET_PLATFORM == AX_PLATFORM_IOS)
#    ifndef __ENABLE_COMPATIBILITY_WITH_UNIX_2003__
#        define __ENABLE_COMPATIBILITY_WITH_UNIX_2003__
#        include <stdio.h>
#        include <dirent.h>
FILE* fopen$UNIX2003(const char* filename, const char* mode)
{
    return fopen(filename, mode);
}
size_t fwrite$UNIX2003(const void* a, size_t b, size_t c, FILE* d)
{
    return fwrite(a, b, c, d);
}
int fputs$UNIX2003(const char* res1, FILE* res2)
{
    return fputs(res1, res2);
}
char* strerror$UNIX2003(int errnum)
{
    return strerror(errnum);
}
DIR* opendir$INODE64$UNIX2003(char* dirName)
{
    return opendir(dirName);
}
DIR* opendir$INODE64(char* dirName)
{
    return opendir(dirName);
}

int closedir$UNIX2003(DIR* dir)
{
    return closedir(dir);
}

struct dirent* readdir$INODE64(DIR* dir)
{
    return readdir(dir);
}
#    endif
#endif

#if AX_USE_PNG && !AX_USE_WIC
#    include "png.h"
#endif  // AX_USE_PNG

#if AX_USE_JPEG && !AX_USE_WIC
#    include "jpeglib.h"
#    include <setjmp.h>
#endif  // AX_USE_JPEG
} /* extern "C" */

#include "base/ktxspec_v1.h"

#include "base/s3tc.h"
#include "base/atitc.h"
#include "base/pvr.h"
#include "base/TGAlib.h"

#include "base/etc1.h"
#include "base/etc2.h"

#include "base/astc.h"

#if AX_USE_WEBP
#    include "decode.h"
#endif  // AX_USE_WEBP

#include "base/Macros.h"
#include "platform/Common.h"
#include "platform/StdC.h"
#include "platform/FileUtils.h"
#include "base/Configuration.h"
#include "base/Utils.h"
#include "base/ZipUtils.h"
#if (AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID)
#    include "platform/android/FileUtils-android.h"
#    include "platform/GL.h"
#endif

#if AX_USE_WIC
#    include "platform/winrt/WICImageLoader-winrt.h"
#endif

NS_AX_BEGIN

//////////////////////////////////////////////////////////////////////////
// struct and data for pvr structure

namespace
{
static const int PVR_TEXTURE_FLAG_TYPE_MASK = 0xff;

// Values taken from PVRTexture.h from http://www.imgtec.com
enum class PVR2TextureFlag
{
    Mipmap       = (1 << 8),   // has mip map levels
    Twiddle      = (1 << 9),   // is twiddled
    Bumpmap      = (1 << 10),  // has normals encoded for a bump map
    Tiling       = (1 << 11),  // is bordered for tiled pvr
    Cubemap      = (1 << 12),  // is a cubemap/skybox
    FalseMipCol  = (1 << 13),  // are there false colored MIP levels
    Volume       = (1 << 14),  // is this a volume texture
    Alpha        = (1 << 15),  // v2.1 is there transparency info in the texture
    VerticalFlip = (1 << 16),  // v2.1 is the texture vertically flipped
};

enum class PVR3TextureFlag
{
    PremultipliedAlpha = (1 << 1)  // has premultiplied alpha
};

static const char gPVRTexIdentifier[5] = "PVR!";

// v2
enum class PVR2TexturePixelFormat : uint8_t
{
    RGBA4444 = 0x10,
    RGBA5551,
    RGBA8888,
    RGB565,
    RGB555,  // unsupported
    RGB888,
    I8,
    AI88,
    PVRTC2BPP_RGBA,
    PVRTC4BPP_RGBA,
    BGRA8888,
    A8,
};

// v3
enum class PVR3TexturePixelFormat : uint64_t
{
    PVRTC2BPP_RGB     = 0ULL,
    PVRTC2BPP_RGBA    = 1ULL,
    PVRTC4BPP_RGB     = 2ULL,
    PVRTC4BPP_RGBA    = 3ULL,
    PVRTC2_2BPP_RGBA  = 4ULL,
    PVRTC2_4BPP_RGBA  = 5ULL,
    ETC1              = 6ULL,
    DXT1              = 7ULL,
    DXT2              = 8ULL,
    DXT3              = 9ULL,
    DXT4              = 10ULL,
    DXT5              = 11ULL,
    BC1               = 7ULL,
    BC2               = 9ULL,
    BC3               = 11ULL,
    BC4               = 12ULL,
    BC5               = 13ULL,
    BC6               = 14ULL,
    BC7               = 15ULL,
    UYVY              = 16ULL,
    YUY2              = 17ULL,
    BW1bpp            = 18ULL,
    R9G9B9E5          = 19ULL,
    RGBG8888          = 20ULL,
    GRGB8888          = 21ULL,
    ETC2_RGB          = 22ULL,
    ETC2_RGBA         = 23ULL,
    ETC2_RGBA1        = 24ULL,
    EAC_R11_Unsigned  = 25ULL,
    EAC_R11_Signed    = 26ULL,
    EAC_RG11_Unsigned = 27ULL,
    EAC_RG11_Signed   = 28ULL,

    BGRA8888 = 0x0808080861726762ULL,
    RGBA8888 = 0x0808080861626772ULL,
    RGBA4444 = 0x0404040461626772ULL,
    RGBA5551 = 0x0105050561626772ULL,
    RGB565   = 0x0005060500626772ULL,
    RGB888   = 0x0008080800626772ULL,
    A8       = 0x0000000800000061ULL,
    L8       = 0x000000080000006cULL,
    LA88     = 0x000008080000616cULL,
};

// v2
typedef const std::map<PVR2TexturePixelFormat, backend::PixelFormat> _pixel2_formathash;

static const _pixel2_formathash::value_type v2_pixel_formathash_value[] = {
    _pixel2_formathash::value_type(PVR2TexturePixelFormat::BGRA8888, backend::PixelFormat::BGRA8),
    _pixel2_formathash::value_type(PVR2TexturePixelFormat::RGBA8888, backend::PixelFormat::RGBA8),
    _pixel2_formathash::value_type(PVR2TexturePixelFormat::RGBA4444, backend::PixelFormat::RGBA4),
    _pixel2_formathash::value_type(PVR2TexturePixelFormat::RGBA5551, backend::PixelFormat::RGB5A1),
    _pixel2_formathash::value_type(PVR2TexturePixelFormat::RGB565, backend::PixelFormat::RGB565),
    _pixel2_formathash::value_type(PVR2TexturePixelFormat::RGB888, backend::PixelFormat::RGB8),
    _pixel2_formathash::value_type(PVR2TexturePixelFormat::A8, backend::PixelFormat::A8),
    _pixel2_formathash::value_type(PVR2TexturePixelFormat::I8, backend::PixelFormat::L8),
    _pixel2_formathash::value_type(PVR2TexturePixelFormat::AI88, backend::PixelFormat::LA8),

    _pixel2_formathash::value_type(PVR2TexturePixelFormat::PVRTC2BPP_RGBA, backend::PixelFormat::PVRTC2A),
    _pixel2_formathash::value_type(PVR2TexturePixelFormat::PVRTC4BPP_RGBA, backend::PixelFormat::PVRTC4A),
};

static const int PVR2_MAX_TABLE_ELEMENTS = sizeof(v2_pixel_formathash_value) / sizeof(v2_pixel_formathash_value[0]);
static const _pixel2_formathash v2_pixel_formathash(v2_pixel_formathash_value,
                                                    v2_pixel_formathash_value + PVR2_MAX_TABLE_ELEMENTS);

// v3
typedef const std::map<PVR3TexturePixelFormat, backend::PixelFormat> _pixel3_formathash;
static _pixel3_formathash::value_type v3_pixel_formathash_value[] = {
    _pixel3_formathash::value_type(PVR3TexturePixelFormat::BGRA8888, backend::PixelFormat::BGRA8),
    _pixel3_formathash::value_type(PVR3TexturePixelFormat::RGBA8888, backend::PixelFormat::RGBA8),
    _pixel3_formathash::value_type(PVR3TexturePixelFormat::RGBA4444, backend::PixelFormat::RGBA4),
    _pixel3_formathash::value_type(PVR3TexturePixelFormat::RGBA5551, backend::PixelFormat::RGB5A1),
    _pixel3_formathash::value_type(PVR3TexturePixelFormat::RGB565, backend::PixelFormat::RGB565),
    _pixel3_formathash::value_type(PVR3TexturePixelFormat::RGB888, backend::PixelFormat::RGB8),
    _pixel3_formathash::value_type(PVR3TexturePixelFormat::A8, backend::PixelFormat::A8),
    _pixel3_formathash::value_type(PVR3TexturePixelFormat::L8, backend::PixelFormat::L8),
    _pixel3_formathash::value_type(PVR3TexturePixelFormat::LA88, backend::PixelFormat::LA8),

    _pixel3_formathash::value_type(PVR3TexturePixelFormat::PVRTC2BPP_RGB, backend::PixelFormat::PVRTC2),
    _pixel3_formathash::value_type(PVR3TexturePixelFormat::PVRTC2BPP_RGBA, backend::PixelFormat::PVRTC2A),
    _pixel3_formathash::value_type(PVR3TexturePixelFormat::PVRTC4BPP_RGB, backend::PixelFormat::PVRTC4),
    _pixel3_formathash::value_type(PVR3TexturePixelFormat::PVRTC4BPP_RGBA, backend::PixelFormat::PVRTC4A),

    _pixel3_formathash::value_type(PVR3TexturePixelFormat::ETC1, backend::PixelFormat::ETC1),
    _pixel3_formathash::value_type(PVR3TexturePixelFormat::ETC2_RGB, backend::PixelFormat::ETC2_RGB),
    _pixel3_formathash::value_type(PVR3TexturePixelFormat::ETC2_RGBA, backend::PixelFormat::ETC2_RGBA),
};

static const int PVR3_MAX_TABLE_ELEMENTS = sizeof(v3_pixel_formathash_value) / sizeof(v3_pixel_formathash_value[0]);

static const _pixel3_formathash v3_pixel_formathash(v3_pixel_formathash_value,
                                                    v3_pixel_formathash_value + PVR3_MAX_TABLE_ELEMENTS);

typedef struct _PVRTexHeader
{
    unsigned int headerLength;
    unsigned int height;
    unsigned int width;
    unsigned int numMipmaps;
    unsigned int flags;
    unsigned int dataLength;
    unsigned int bpp;
    unsigned int bitmaskRed;
    unsigned int bitmaskGreen;
    unsigned int bitmaskBlue;
    unsigned int bitmaskAlpha;
    unsigned int pvrTag;
    unsigned int numSurfs;
} PVRv2TexHeader;

#ifdef _MSC_VER
#    pragma pack(push, 1)
#endif
typedef struct
{
    uint32_t version;
    uint32_t flags;
    uint64_t pixelFormat;
    uint32_t colorSpace;
    uint32_t channelType;
    uint32_t height;
    uint32_t width;
    uint32_t depth;
    uint32_t numberOfSurfaces;
    uint32_t numberOfFaces;
    uint32_t numberOfMipmaps;
    uint32_t metadataLength;
#ifdef _MSC_VER
} PVRv3TexHeader;
#    pragma pack(pop)
#else
} __attribute__((packed)) PVRv3TexHeader;
#endif
}  // namespace
// pvr structure end

//////////////////////////////////////////////////////////////////////////

// struct and data for s3tc(dds) struct
namespace
{
struct DDColorKey
{
    uint32_t colorSpaceLowValue;
    uint32_t colorSpaceHighValue;
};

struct DDSCaps
{
    uint32_t caps;
    uint32_t caps2;
    uint32_t caps3;
    uint32_t caps4;
};

struct DDPixelFormat
{
    uint32_t size;
    uint32_t flags;
    uint32_t fourCC;
    uint32_t RGBBitCount;
    uint32_t RBitMask;
    uint32_t GBitMask;
    uint32_t BBitMask;
    uint32_t ABitMask;
};

struct DDSURFACEDESC2
{
    uint32_t size;
    uint32_t flags;
    uint32_t height;
    uint32_t width;

    union
    {
        uint32_t pitch;
        uint32_t linearSize;
    } DUMMYUNIONNAMEN1;

    union
    {
        uint32_t backBufferCount;
        uint32_t depth;
    } DUMMYUNIONNAMEN5;

    union
    {
        uint32_t mipMapCount;
        uint32_t refreshRate;
        uint32_t srcVBHandle;
    } DUMMYUNIONNAMEN2;

    uint32_t alphaBitDepth;
    uint32_t reserved;
    uint32_t surface;

    union
    {
        DDColorKey ddckCKDestOverlay;
        uint32_t emptyFaceColor;
    } DUMMYUNIONNAMEN3;

    DDColorKey ddckCKDestBlt;
    DDColorKey ddckCKSrcOverlay;
    DDColorKey ddckCKSrcBlt;

    union
    {
        DDPixelFormat ddpfPixelFormat;
        uint32_t FVF;
    } DUMMYUNIONNAMEN4;

    DDSCaps ddsCaps;
    uint32_t textureStage;
};

#pragma pack(push, 1)

struct S3TCTexHeader
{
    char fileCode[4];
    DDSURFACEDESC2 ddsd;
};

#pragma pack(pop)

}  // namespace
// s3tc struct end

//////////////////////////////////////////////////////////////////////////

namespace
{
typedef struct
{
    const uint8_t* data;
    ssize_t size;
    int offset;
} tImageSource;

#if AX_USE_PNG && !AX_USE_WIC
void pngWriteCallback(png_structp png_ptr, png_bytep data, size_t length)
{
    if (png_ptr == NULL)
        return;

    auto fileStream = (IFileStream*)png_get_io_ptr(png_ptr);

    const auto check = fileStream->write(data, static_cast<unsigned int>(length));

    if (check != length)
        png_error(png_ptr, "Write Error");
}

static void pngReadCallback(png_structp png_ptr, png_bytep data, png_size_t length)
{
    tImageSource* isource = (tImageSource*)png_get_io_ptr(png_ptr);

    if ((int)(isource->offset + length) <= isource->size)
    {
        memcpy(data, isource->data + isource->offset, length);
        isource->offset += length;
    }
    else
    {
        png_error(png_ptr, "pngReaderCallback failed");
    }
}
#endif  // AX_USE_PNG
}  // namespace

/*
 * Notes: PVR file Specification have many pixel formats, cocos2d-x-v2~v4 and axmol only support pvrtc and etc1
 * see: https://cdn.imgtec.com/sdk-documentation/PVR+File+Format.Specification.pdf
 */
static backend::PixelFormat getDevicePVRPixelFormat(backend::PixelFormat format)
{
    switch (format)
    {
    case backend::PixelFormat::PVRTC4:
    case backend::PixelFormat::PVRTC4A:
    case backend::PixelFormat::PVRTC2:
    case backend::PixelFormat::PVRTC2A:
        if (Configuration::getInstance()->supportsPVRTC())
            return format;
        else
            return backend::PixelFormat::RGBA8;
    case backend::PixelFormat::ETC1:
        if (Configuration::getInstance()->supportsETC1())
            return format;
        else if (Configuration::getInstance()->supportsETC2())
            return backend::PixelFormat::ETC2_RGB;
        else
            return backend::PixelFormat::RGBA8;
    default:
        return format;
    }
}

namespace
{
bool testFormatForPvr2TCSupport(PVR2TexturePixelFormat /*format*/)
{
    return true;
}

bool testFormatForPvr3TCSupport(PVR3TexturePixelFormat format)
{
    switch (format)
    {
    case PVR3TexturePixelFormat::ETC2_RGBA:
    case PVR3TexturePixelFormat::ETC2_RGB:
        return Configuration::getInstance()->supportsETC2();
    case PVR3TexturePixelFormat::DXT1:
    case PVR3TexturePixelFormat::DXT3:
    case PVR3TexturePixelFormat::DXT5:
        return Configuration::getInstance()->supportsS3TC();

    case PVR3TexturePixelFormat::BGRA8888:
        return Configuration::getInstance()->supportsBGRA8888();

    case PVR3TexturePixelFormat::PVRTC2BPP_RGB:
    case PVR3TexturePixelFormat::PVRTC2BPP_RGBA:
    case PVR3TexturePixelFormat::PVRTC4BPP_RGB:
    case PVR3TexturePixelFormat::PVRTC4BPP_RGBA:
    case PVR3TexturePixelFormat::ETC1:
    case PVR3TexturePixelFormat::RGBA8888:
    case PVR3TexturePixelFormat::RGBA4444:
    case PVR3TexturePixelFormat::RGBA5551:
    case PVR3TexturePixelFormat::RGB565:
    case PVR3TexturePixelFormat::RGB888:
    case PVR3TexturePixelFormat::A8:
    case PVR3TexturePixelFormat::L8:
    case PVR3TexturePixelFormat::LA88:
        return true;

    default:
        return false;
    }
}
}  // namespace

namespace
{
static uint32_t makeFourCC(char ch0, char ch1, char ch2, char ch3)
{
    const uint32_t fourCC = ((uint32_t)(char)(ch0) | ((uint32_t)(char)(ch1) << 8) | ((uint32_t)(char)(ch2) << 16) |
                             ((uint32_t)(char)(ch3) << 24));
    return fourCC;
}
}  // namespace

//////////////////////////////////////////////////////////////////////////
// Implement Image
//////////////////////////////////////////////////////////////////////////
bool Image::PNG_PREMULTIPLIED_ALPHA_ENABLED = true;
uint32_t Image::COMPRESSED_IMAGE_PMA_FLAGS  = Image::CompressedImagePMAFlag::DUAL_SAMPLER;

void Image::setCompressedImagesHavePMA(uint32_t targets, bool havePMA)
{
    if (havePMA)
        COMPRESSED_IMAGE_PMA_FLAGS |= targets;
    else
        COMPRESSED_IMAGE_PMA_FLAGS &= ~targets;
}

bool Image::isCompressedImageHavePMA(uint32_t target)
{
    return target & COMPRESSED_IMAGE_PMA_FLAGS;
}

Image::Image()
    : _data(nullptr)
    , _dataLen(0)
    , _offset(0)
    , _width(0)
    , _height(0)
    , _unpack(false)
    , _fileType(Format::UNKNOWN)
    , _pixelFormat(backend::PixelFormat::NONE)
    , _numberOfMipmaps(0)
    , _hasPremultipliedAlpha(false)
{}

Image::~Image()
{
    if (!_unpack)
    {
        AX_SAFE_FREE(_data);
    }
    else
    {
        for (int i = 0; i < _numberOfMipmaps; ++i)
            AX_SAFE_FREE(_mipmaps[i].address);
    }
}

bool Image::initWithImageFile(std::string_view path)
{
    bool ret  = false;
    _filePath = FileUtils::getInstance()->fullPathForFilename(path);

    Data data = FileUtils::getInstance()->getDataFromFile(_filePath);

    if (!data.isNull())
    {
        ssize_t n = 0;
        auto buf  = data.takeBuffer(&n);
        ret       = initWithImageData(buf, n, true);
    }

    return ret;
}

bool Image::initWithImageFileThreadSafe(std::string_view fullpath)
{
    bool ret  = false;
    _filePath = fullpath;

    Data data = FileUtils::getInstance()->getDataFromFile(_filePath);

    if (!data.isNull())
    {
        ssize_t n = 0;
        auto buf  = data.takeBuffer(&n);
        ret       = initWithImageData(buf, n, true);
    }

    return ret;
}

bool Image::initWithImageData(const uint8_t* data, ssize_t dataLen)
{
    return initWithImageData(const_cast<uint8_t*>(data), dataLen, false);
}

bool Image::initWithImageData(uint8_t* data, ssize_t dataLen, bool ownData)
{
    bool ret = false;

    do
    {
        AX_BREAK_IF(!data || dataLen == 0);

        uint8_t* unpackedData = nullptr;
        ssize_t unpackedLen   = 0;

        // detect and unzip the compress file
        if (ZipUtils::isCCZBuffer(data, dataLen))
        {
            unpackedLen = ZipUtils::inflateCCZBuffer(data, dataLen, &unpackedData);
        }
        else if (ZipUtils::isGZipBuffer(data, dataLen))
        {
            unpackedLen = ZipUtils::inflateMemory(const_cast<uint8_t*>(data), dataLen, &unpackedData);
        }
        else
        {
            unpackedData = const_cast<uint8_t*>(data);
            unpackedLen  = dataLen;
        }

        if (unpackedData != data)
        {  // free old data and own the unpackedData
            if (ownData)
                free((void*)data);
            ownData = true;
        }

        _fileType = detectFormat(unpackedData, unpackedLen);

        switch (_fileType)
        {
        case Format::PNG:
            ret = initWithPngData(unpackedData, unpackedLen);
            break;
        case Format::JPG:
            ret = initWithJpgData(unpackedData, unpackedLen);
            break;
        case Format::WEBP:
            ret = initWithWebpData(unpackedData, unpackedLen);
            break;
        case Format::PVR:
            ret = initWithPVRData(unpackedData, unpackedLen, ownData);
            break;
        case Format::ETC1:
            ret = initWithETCData(unpackedData, unpackedLen, ownData);
            break;
        case Format::ETC2:
            ret = initWithETC2Data(unpackedData, unpackedLen, ownData);
            break;
        case Format::S3TC:
            ret = initWithS3TCData(unpackedData, unpackedLen, ownData);
            break;
        case Format::ATITC:
            ret = initWithATITCData(unpackedData, unpackedLen, ownData);
            break;
        case Format::ASTC:
            ret = initWithASTCData(unpackedData, unpackedLen, ownData);
            break;
        case Format::BMP:
            ret = initWithBmpData(unpackedData, unpackedLen);
            break;
        default:
        {
            // load and detect image format
            tImageTGA* tgaData = tgaLoadBuffer(unpackedData, static_cast<int32_t>(unpackedLen));

            if (tgaData != nullptr && tgaData->status == TGA_OK)
            {
                ret = initWithTGAData(tgaData);
            }
            else
            {
                AXLOG("axmol: unsupported image format!");
            }

            free(tgaData);
            break;
        }
        }

        if (_data != unpackedData && ownData)
            free(unpackedData);
        // else, the hardware texture decoder used, the compressed data was stored directly
    } while (0);

    return ret;
}

bool Image::initWithRawData(const uint8_t* data,
                            ssize_t /*dataLen*/,
                            int width,
                            int height,
                            int /*bitsPerComponent*/,
                            bool preMulti)
{
    bool ret = false;
    do
    {
        AX_BREAK_IF(0 == width || 0 == height);

        _height                = height;
        _width                 = width;
        _hasPremultipliedAlpha = preMulti;
        _pixelFormat           = backend::PixelFormat::RGBA8;

        // only RGBA8888 supported
        int bytesPerComponent = 4;
        _dataLen              = height * width * bytesPerComponent;
        _data                 = static_cast<uint8_t*>(malloc(_dataLen));
        AX_BREAK_IF(!_data);
        memcpy(_data, data, _dataLen);

        ret = true;
    } while (0);

    return ret;
}

bool Image::isPng(const uint8_t* data, ssize_t dataLen)
{
    if (dataLen <= 8)
    {
        return false;
    }

    static const uint8_t PNG_SIGNATURE[] = {0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a};

    return memcmp(PNG_SIGNATURE, data, sizeof(PNG_SIGNATURE)) == 0;
}

bool Image::isBmp(const uint8_t* data, ssize_t dataLen)
{
    return dataLen > 54 && data[0] == 'B' && data[1] == 'M';
}

bool Image::isEtc1(const uint8_t* data, ssize_t /*dataLen*/)
{
    return !!etc1_pkm_is_valid((etc1_byte*)data);
}

bool Image::isEtc2(const uint8_t* data, ssize_t dataLen)
{
    return !!etc2_pkm_is_valid((etc2_byte*)data);
}

bool Image::isS3TC(const uint8_t* data, ssize_t /*dataLen*/)
{

    S3TCTexHeader* header = (S3TCTexHeader*)data;

    return (strncmp(header->fileCode, "DDS", 3) == 0);
}

bool Image::isASTC(const uint8_t* data, ssize_t /*dataLen*/)
{
    astc_header* hdr = (astc_header*)data;

    uint32_t magicval = astc_unpack_bytes(hdr->magic[0], hdr->magic[1], hdr->magic[2], hdr->magic[3]);

    return (magicval & 0x0FFFFFFF) == (ASTC_MAGIC_ID & 0x0FFFFFFF);  // wildcard check
}

bool Image::isJpg(const uint8_t* data, ssize_t dataLen)
{
    if (dataLen <= 4)
    {
        return false;
    }

    static const uint8_t JPG_SOI[] = {0xFF, 0xD8};

    return memcmp(data, JPG_SOI, 2) == 0;
}

bool Image::isWebp(const uint8_t* data, ssize_t dataLen)
{
    if (dataLen <= 12)
    {
        return false;
    }

    static const char* WEBP_RIFF = "RIFF";
    static const char* WEBP_WEBP = "WEBP";

    return memcmp(data, WEBP_RIFF, 4) == 0 && memcmp(static_cast<const uint8_t*>(data) + 8, WEBP_WEBP, 4) == 0;
}

bool Image::isPvr(const uint8_t* data, ssize_t dataLen)
{
    if (static_cast<size_t>(dataLen) < sizeof(PVRv2TexHeader) || static_cast<size_t>(dataLen) < sizeof(PVRv3TexHeader))
    {
        return false;
    }

    const PVRv2TexHeader* headerv2 = static_cast<const PVRv2TexHeader*>(static_cast<const void*>(data));
    const PVRv3TexHeader* headerv3 = static_cast<const PVRv3TexHeader*>(static_cast<const void*>(data));

    return memcmp(&headerv2->pvrTag, gPVRTexIdentifier, strlen(gPVRTexIdentifier)) == 0 ||
           AX_SWAP_INT32_BIG_TO_HOST(headerv3->version) == 0x50565203;
}

Image::Format Image::detectFormat(const uint8_t* data, ssize_t dataLen)
{
    if (isPng(data, dataLen))
    {
        return Format::PNG;
    }
    else if (isJpg(data, dataLen))
    {
        return Format::JPG;
    }
    else if (isBmp(data, dataLen))
    {
        return Format::BMP;
    }
    else if (isWebp(data, dataLen))
    {
        return Format::WEBP;
    }
    else if (isPvr(data, dataLen))
    {
        return Format::PVR;
    }
    else if (isEtc1(data, dataLen))
    {
        return Format::ETC1;
    }
    else if (isEtc2(data, dataLen))
    {
        return Format::ETC2;
    }
    else if (isS3TC(data, dataLen))
    {
        return Format::S3TC;
    }
    else if (isASTC(data, dataLen))
    {
        return Format::ASTC;
    }
    else if (dataLen >= KTX_V1_HEADER_SIZE)
    {  // Check whether ktxspec v1.1 file format
        auto header = (KTXv1Header*)data;
        if (memcmp(&header->identifier[1], KTX_V1_MAGIC, sizeof(KTX_V1_MAGIC) - 1) == 0)
        {
            switch (header->glInternalFormat)
            {
            case KTXv1Header::InternalFormat::ATC_RGB_AMD:
            case KTXv1Header::InternalFormat::ATC_RGBA_INTERPOLATED_ALPHA_AMD:
            case KTXv1Header::InternalFormat::ATC_RGBA_EXPLICIT_ALPHA_AMD:
                return Format::ATITC;
            case KTXv1Header::InternalFormat::ETC2_RGB8:
            case KTXv1Header::InternalFormat::ETC2_RGBA8:
                return Format::ETC2;
            case KTXv1Header::InternalFormat::ETC1_RGB8:
                return Format::ETC1;
            default:;
            }
        }
    }

    return Format::UNKNOWN;
}

int Image::getBitPerPixel()
{
    return backend::PixelFormatUtils::getFormatDescriptor(_pixelFormat).bpp;
}

bool Image::hasAlpha()
{
    return backend::PixelFormatUtils::getFormatDescriptor(_pixelFormat).alpha;
}

bool Image::isCompressed()
{
    return backend::PixelFormatUtils::isCompressed(_pixelFormat);
}

#if AX_USE_WIC
bool Image::decodeWithWIC(const unsigned char* data, ssize_t dataLen)
{
    bool bRet = false;
    WICImageLoader img;

    if (img.decodeImageData(data, dataLen))
    {
        _width  = img.getWidth();
        _height = img.getHeight();

        WICPixelFormatGUID format = img.getPixelFormat();

        if (memcmp(&format, &GUID_WICPixelFormat8bppGray, sizeof(WICPixelFormatGUID)) == 0)
        {
            _pixelFormat = backend::PixelFormat::L8;
        }

        if (memcmp(&format, &GUID_WICPixelFormat8bppAlpha, sizeof(WICPixelFormatGUID)) == 0)
        {
            _pixelFormat = backend::PixelFormat::LA8;
        }

        if (memcmp(&format, &GUID_WICPixelFormat24bppRGB, sizeof(WICPixelFormatGUID)) == 0)
        {
            _pixelFormat = backend::PixelFormat::RGB8;
        }

        if (memcmp(&format, &GUID_WICPixelFormat32bppRGBA, sizeof(WICPixelFormatGUID)) == 0)
        {
            _pixelFormat = backend::PixelFormat::RGBA8;
        }

        if (memcmp(&format, &GUID_WICPixelFormat32bppBGRA, sizeof(WICPixelFormatGUID)) == 0)
        {
            _pixelFormat = backend::PixelFormat::BGRA8;
        }

        _dataLen = img.getImageDataSize();

        AXASSERT(_dataLen > 0, "Image: Decompressed data length is invalid");

        _data = new (std::nothrow) unsigned char[_dataLen];
        bRet  = (img.getImageData(_data, _dataLen) > 0);

        if (_pixelFormat == backend::PixelFormat::RGBA8)
        {
            premultiplyAlpha();
        }
    }

    return bRet;
}

bool Image::encodeWithWIC(std::string_view filePath, bool isToRGB, GUID containerFormat)
{
    // Save formats supported by WIC
    WICPixelFormatGUID targetFormat = isToRGB ? GUID_WICPixelFormat24bppBGR : GUID_WICPixelFormat32bppBGRA;
    unsigned char* pSaveData        = nullptr;
    int saveLen                     = _dataLen;
    int bpp                         = 4;

    if (targetFormat == GUID_WICPixelFormat24bppBGR && _pixelFormat == backend::PixelFormat::RGBA8)
    {
        bpp       = 3;
        saveLen   = _width * _height * bpp;
        pSaveData = new (std::nothrow) unsigned char[saveLen];
        int indL = 0, indR = 0;

        while (indL < saveLen && indR < _dataLen)
        {
            memcpy(&pSaveData[indL], &_data[indR], 3);
            indL += 3;
            indR += 4;
        }
    }
    else
    {
        pSaveData = new (std::nothrow) unsigned char[saveLen];
        memcpy(pSaveData, _data, saveLen);
    }

    for (int ind = 2; ind < saveLen; ind += bpp)
    {
        std::swap(pSaveData[ind - 2], pSaveData[ind]);
    }

    WICImageLoader img;
    bool bRet = img.encodeImageData(filePath, pSaveData, saveLen, targetFormat, _width, _height, containerFormat);

    delete[] pSaveData;
    return bRet;
}

#endif  // AX_USE_WIC

namespace
{
/*
 * ERROR HANDLING:
 *
 * The JPEG library's standard error handler (jerror.c) is divided into
 * several "methods" which you can override individually.  This lets you
 * adjust the behavior without duplicating a lot of code, which you might
 * have to update with each future release.
 *
 * We override the "error_exit" method so that control is returned to the
 * library's caller when a fatal error occurs, rather than calling exit()
 * as the standard error_exit method does.
 *
 * We use C's setjmp/longjmp facility to return control.  This means that the
 * routine which calls the JPEG library must first execute a setjmp() call to
 * establish the return point.  We want the replacement error_exit to do a
 * longjmp().  But we need to make the setjmp buffer accessible to the
 * error_exit routine.  To do this, we make a private extension of the
 * standard JPEG error handler object.  (If we were using C++, we'd say we
 * were making a subclass of the regular error handler.)
 *
 * Here's the extended error handler struct:
 */
#if AX_USE_JPEG && AX_TARGET_PLATFORM != AX_PLATFORM_WINRT
struct MyErrorMgr
{
    struct jpeg_error_mgr pub; /* "public" fields */
    jmp_buf setjmp_buffer;     /* for return to caller */
};

typedef struct MyErrorMgr* MyErrorPtr;

/*
 * Here's the routine that will replace the standard error_exit method:
 */

METHODDEF(void)
myErrorExit(j_common_ptr cinfo)
{
    /* cinfo->err really points to a MyErrorMgr struct, so coerce pointer */
    MyErrorPtr myerr = (MyErrorPtr)cinfo->err;

    /* Always display the message. */
    /* We could postpone this until after returning, if we chose. */
    /* internal message function can't show error message in some platforms, so we rewrite it here.
     * edit it if has version conflict.
     */
    //(*cinfo->err->output_message) (cinfo);
    char buffer[JMSG_LENGTH_MAX];
    (*cinfo->err->format_message)(cinfo, buffer);
    AXLOG("jpeg error: %s", buffer);

    /* Return control to the setjmp point */
    longjmp(myerr->setjmp_buffer, 1);
}
#endif  // AX_USE_JPEG
}  // namespace

bool Image::initWithJpgData(uint8_t* data, ssize_t dataLen)
{
#if AX_USE_WIC
    return decodeWithWIC(data, dataLen);
#elif AX_USE_JPEG
    /* these are standard libjpeg structures for reading(decompression) */
    struct jpeg_decompress_struct cinfo;
    /* We use our private extension JPEG error handler.
     * Note that this struct must live as long as the main JPEG parameter
     * struct, to avoid dangling-pointer problems.
     */
    struct MyErrorMgr jerr;
    /* libjpeg data structure for storing one row, that is, scanline of an image */
    JSAMPROW row_pointer[1] = {0};
    uint32_t location       = 0;

    bool ret = false;
    do
    {
        /* We set up the normal JPEG error routines, then override error_exit. */
        cinfo.err           = jpeg_std_error(&jerr.pub);
        jerr.pub.error_exit = myErrorExit;
        /* Establish the setjmp return context for MyErrorExit to use. */
        if (setjmp(jerr.setjmp_buffer))
        {
            /* If we get here, the JPEG code has signaled an error.
             * We need to clean up the JPEG object, close the input file, and return.
             */
            jpeg_destroy_decompress(&cinfo);
            break;
        }

        /* setup decompression process and source, then read JPEG header */
        jpeg_create_decompress(&cinfo);

#        ifndef AX_TARGET_QT5
        jpeg_mem_src(&cinfo, const_cast<uint8_t*>(data), dataLen);
#        endif /* AX_TARGET_QT5 */

        /* reading the image header which contains image information */
#        if (JPEG_LIB_VERSION >= 90)
        // libjpeg 0.9 adds stricter types.
        jpeg_read_header(&cinfo, TRUE);
#        else
        jpeg_read_header(&cinfo, TRUE);
#        endif  //(JPEG_LIB_VERSION >= 90)

        // we only support RGB or grayscale
        if (cinfo.jpeg_color_space == JCS_GRAYSCALE)
        {
            _pixelFormat = backend::PixelFormat::L8;
        }
        else
        {
            cinfo.out_color_space = JCS_RGB;
            _pixelFormat          = backend::PixelFormat::RGB8;
        }

        /* Start decompression jpeg here */
        jpeg_start_decompress(&cinfo);

        /* init image info */
        _width  = cinfo.output_width;
        _height = cinfo.output_height;

        _dataLen = cinfo.output_width * cinfo.output_height * cinfo.output_components;
        _data    = static_cast<uint8_t*>(malloc(_dataLen));
        AX_BREAK_IF(!_data);

        /* now actually read the jpeg into the raw buffer */
        /* read one scan line at a time */
        while (cinfo.output_scanline < cinfo.output_height)
        {
            row_pointer[0] = _data + location;
            location += cinfo.output_width * cinfo.output_components;
            jpeg_read_scanlines(&cinfo, row_pointer, 1);
        }

        /* When read image file with broken data, jpeg_finish_decompress() may cause error.
         * Besides, jpeg_destroy_decompress() shall deallocate and release all memory associated
         * with the decompression object.
         * So it doesn't need to call jpeg_finish_decompress().
         */
        // jpeg_finish_decompress( &cinfo );
        jpeg_destroy_decompress(&cinfo);
        /* wrap up decompression, destroy objects, free pointers and close open files */
        ret = true;
    } while (0);

    return ret;
#else
    AXLOG("jpeg is not enabled, please enable it in ccConfig.h");
    return false;
#endif  // AX_USE_JPEG
}

bool Image::initWithPngData(uint8_t* data, ssize_t dataLen)
{
#if AX_USE_WIC
    return decodeWithWIC(data, dataLen);
#elif AX_USE_PNG
    // length of bytes to check if it is a valid png file
#        define PNGSIGSIZE 8
    bool ret                    = false;
    png_byte header[PNGSIGSIZE] = {0};
    png_structp png_ptr         = 0;
    png_infop info_ptr          = 0;

    do
    {
        // png header len is 8 bytes
        AX_BREAK_IF(dataLen < PNGSIGSIZE);

        // check the data is png or not
        memcpy(header, data, PNGSIGSIZE);
        AX_BREAK_IF(png_sig_cmp(header, 0, PNGSIGSIZE));

        // init png_struct
        png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
        AX_BREAK_IF(!png_ptr);

        // init png_info
        info_ptr = png_create_info_struct(png_ptr);
        AX_BREAK_IF(!info_ptr);

        AX_BREAK_IF(setjmp(png_jmpbuf(png_ptr)));

        // set the read call back function
        tImageSource imageSource;
        imageSource.data   = (uint8_t*)data;
        imageSource.size   = dataLen;
        imageSource.offset = 0;
        png_set_read_fn(png_ptr, &imageSource, pngReadCallback);

        // read png header info

        // read png file info
        png_read_info(png_ptr, info_ptr);

        _width                 = png_get_image_width(png_ptr, info_ptr);
        _height                = png_get_image_height(png_ptr, info_ptr);
        png_byte bit_depth     = png_get_bit_depth(png_ptr, info_ptr);
        png_uint_32 color_type = png_get_color_type(png_ptr, info_ptr);

        // AXLOG("color type %u", color_type);

        // force palette images to be expanded to 24-bit RGB
        // it may include alpha channel
        if (color_type == PNG_COLOR_TYPE_PALETTE)
        {
            png_set_palette_to_rgb(png_ptr);
        }
        // low-bit-depth grayscale images are to be expanded to 8 bits
        if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
        {
            bit_depth = 8;
            png_set_expand_gray_1_2_4_to_8(png_ptr);
        }
        // expand any tRNS chunk data into a full alpha channel
        if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
        {
            png_set_tRNS_to_alpha(png_ptr);
        }
        // reduce images with 16-bit samples to 8 bits
        if (bit_depth == 16)
        {
            png_set_strip_16(png_ptr);
        }

        // Expanded earlier for grayscale, now take care of palette and rgb
        if (bit_depth < 8)
        {
            png_set_packing(png_ptr);
        }
        // update info
        png_read_update_info(png_ptr, info_ptr);
        color_type = png_get_color_type(png_ptr, info_ptr);

        switch (color_type)
        {
        case PNG_COLOR_TYPE_GRAY:
            _pixelFormat = backend::PixelFormat::L8;
            break;
        case PNG_COLOR_TYPE_GRAY_ALPHA:
            _pixelFormat = backend::PixelFormat::LA8;
            break;
        case PNG_COLOR_TYPE_RGB:
            _pixelFormat = backend::PixelFormat::RGB8;
            break;
        case PNG_COLOR_TYPE_RGB_ALPHA:
            _pixelFormat = backend::PixelFormat::RGBA8;
            break;
        default:
            break;
        }

        // read png data
        png_size_t rowbytes;
        png_bytep* row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * _height);

        rowbytes = png_get_rowbytes(png_ptr, info_ptr);

        _dataLen = rowbytes * _height;
        _data    = static_cast<uint8_t*>(malloc(_dataLen));
        if (!_data)
        {
            if (row_pointers != nullptr)
            {
                free(row_pointers);
            }
            break;
        }

        for (unsigned short i = 0; i < _height; ++i)
        {
            row_pointers[i] = _data + i * rowbytes;
        }
        png_read_image(png_ptr, row_pointers);

        png_read_end(png_ptr, nullptr);

        // premultiplied alpha for RGBA8888
        if (color_type == PNG_COLOR_TYPE_RGB_ALPHA)
        {
            if (PNG_PREMULTIPLIED_ALPHA_ENABLED)
            {
                premultiplyAlpha();
            }
            else
            {
                // if PNG_PREMULTIPLIED_ALPHA_ENABLED == false && AX_ENABLE_PREMULTIPLIED_ALPHA != 0,
                // you must do PMA at shader, such as modify positionTextureColor.frag
                _hasPremultipliedAlpha = !!AX_ENABLE_PREMULTIPLIED_ALPHA;
            }
        }

        if (row_pointers != nullptr)
        {
            free(row_pointers);
        }

        ret = true;
    } while (0);

    if (png_ptr)
    {
        png_destroy_read_struct(&png_ptr, (info_ptr) ? &info_ptr : 0, 0);
    }
    return ret;
#else
    AXLOG("png is not enabled, please enable it in ccConfig.h");
    return false;
#endif  // AX_USE_PNG
}

bool Image::initWithBmpData(uint8_t* data, ssize_t dataLen)
{
    const int nrChannels = 4;
    _data = stbi_load_from_memory(data, static_cast<int>(dataLen), &_width, &_height, nullptr, nrChannels);
    if (_data)
    {
        _dataLen     = _width * _height * nrChannels;
        _fileType    = Format::BMP;
        _pixelFormat = backend::PixelFormat::RGBA8;
        return true;
    }
    return false;
}

bool Image::initWithWebpData(uint8_t* data, ssize_t dataLen)
{
#if AX_USE_WEBP
    bool ret = false;

    do
    {
        WebPDecoderConfig config;
        if (WebPInitDecoderConfig(&config) == 0)
            break;
        if (WebPGetFeatures(static_cast<const uint8_t*>(data), dataLen, &config.input) != VP8_STATUS_OK)
            break;
        if (config.input.width == 0 || config.input.height == 0)
            break;

        config.output.colorspace = config.input.has_alpha ? MODE_rgbA : MODE_RGB;
        _pixelFormat             = config.input.has_alpha ? backend::PixelFormat::RGBA8 : backend::PixelFormat::RGB8;
        _width                   = config.input.width;
        _height                  = config.input.height;

        // we ask webp to give data with premultiplied alpha
        _hasPremultipliedAlpha = (config.input.has_alpha != 0);

        _dataLen = _width * _height * (config.input.has_alpha ? 4 : 3);
        _data    = static_cast<uint8_t*>(malloc(_dataLen));

        config.output.u.RGBA.rgba        = static_cast<uint8_t*>(_data);
        config.output.u.RGBA.stride      = _width * (config.input.has_alpha ? 4 : 3);
        config.output.u.RGBA.size        = _dataLen;
        config.output.is_external_memory = 1;

        if (WebPDecode(static_cast<const uint8_t*>(data), dataLen, &config) != VP8_STATUS_OK)
        {
            free(_data);
            _data = nullptr;
            break;
        }

        ret = true;
    } while (0);
    return ret;
#else
    AXLOG("webp is not enabled, please enable it in ccConfig.h");
    return false;
#endif  // AX_USE_WEBP
}

bool Image::initWithTGAData(tImageTGA* tgaData)
{
    bool ret = false;

    do
    {
        AX_BREAK_IF(tgaData == nullptr);

        // tgaLoadBuffer only support type 2, 3, 10
        if (2 == tgaData->type || 10 == tgaData->type)
        {
            // true color
            // unsupported RGB555
            if (tgaData->pixelDepth == 16)
            {
                _pixelFormat = backend::PixelFormat::RGB5A1;
            }
            else if (tgaData->pixelDepth == 24)
            {
                _pixelFormat = backend::PixelFormat::RGB8;
            }
            else if (tgaData->pixelDepth == 32)
            {
                _pixelFormat = backend::PixelFormat::RGBA8;
            }
            else
            {
                AXLOG("Image WARNING: unsupported true color tga data pixel format. FILE: %s", _filePath.c_str());
                break;
            }
        }
        else if (3 == tgaData->type)
        {
            // gray
            if (8 == tgaData->pixelDepth)
            {
                _pixelFormat = backend::PixelFormat::L8;
            }
            else
            {
                // actually this won't happen, if it happens, maybe the image file is not a tga
                AXLOG("Image WARNING: unsupported gray tga data pixel format. FILE: %s", _filePath.c_str());
                break;
            }
        }

        _width    = tgaData->width;
        _height   = tgaData->height;
        _data     = tgaData->imageData;
        _dataLen  = _width * _height * tgaData->pixelDepth / 8;
        _fileType = Format::TGA;

        ret = true;

    } while (false);

    if (ret)
    {
        if (FileUtils::getInstance()->getFileExtension(_filePath) != ".tga")
        {
            AXLOG("Image WARNING: the image file suffix is not tga, but parsed as a tga image file. FILE: %s",
                  _filePath.c_str());
        }
    }
    else
    {
        if (tgaData && tgaData->imageData != nullptr)
        {
            free(tgaData->imageData);
            _data = nullptr;
        }
    }

    return ret;
}

bool Image::initWithPVRv2Data(uint8_t* data, ssize_t dataLen, bool ownData)
{
    int blockSize = 0, widthBlocks = 0, heightBlocks = 0;
    int width = 0, height = 0;

    // Cast first sizeof(PVRTexHeader) bytes of data stream as PVRTexHeader
    const PVRv2TexHeader* header = static_cast<const PVRv2TexHeader*>(static_cast<const void*>(data));

    // Make sure that tag is in correct formatting
    if (memcmp(&header->pvrTag, gPVRTexIdentifier, strlen(gPVRTexIdentifier)) != 0)
    {
        return false;
    }

    Configuration* configuration = Configuration::getInstance();

    // can not detect the premultiplied alpha from pvr file, use _PVRHaveAlphaPremultiplied instead.
    _hasPremultipliedAlpha = isCompressedImageHavePMA(CompressedImagePMAFlag::PVR);

    unsigned int flags                 = AX_SWAP_INT32_LITTLE_TO_HOST(header->flags);
    PVR2TexturePixelFormat formatFlags = static_cast<PVR2TexturePixelFormat>(flags & PVR_TEXTURE_FLAG_TYPE_MASK);
    bool flipped                       = (flags & (unsigned int)PVR2TextureFlag::VerticalFlip) ? true : false;
    if (flipped)
    {
        AXLOG("axmol: WARNING: Image is flipped. Regenerate it using PVRTexTool");
    }

    if (!configuration->supportsNPOT() && (static_cast<int>(header->width) != ccNextPOT(header->width) ||
                                           static_cast<int>(header->height) != ccNextPOT(header->height)))
    {
        AXLOG("axmol: ERROR: Loading an NPOT texture (%dx%d) but is not supported on this device", header->width,
              header->height);
        return false;
    }

    if (!testFormatForPvr2TCSupport(formatFlags))
    {
        AXLOG("axmol: WARNING: Unsupported PVR Pixel Format: 0x%02X. Re-encode it with a OpenGL pixel format variant",
              (int)formatFlags);
        return false;
    }

    if (v2_pixel_formathash.find(formatFlags) == v2_pixel_formathash.end())
    {
        AXLOG("axmol: WARNING: Unsupported PVR Pixel Format: 0x%02X. Re-encode it with a OpenGL pixel format variant",
              (int)formatFlags);
        return false;
    }

    auto pixelFormat = getDevicePVRPixelFormat(v2_pixel_formathash.at(formatFlags));
    auto& info       = backend::PixelFormatUtils::getFormatDescriptor(pixelFormat);
    int bpp          = info.bpp;
    if (!bpp)
    {
        AXLOG("axmol: WARNING: Unsupported PVR Pixel Format: 0x%02X. Re-encode it with a OpenGL pixel format variant",
              (int)formatFlags);
        return false;
    }

    _pixelFormat = pixelFormat;

    // Reset num of mipmaps
    _numberOfMipmaps = 0;

    // Get size of mipmap
    _width = width = AX_SWAP_INT32_LITTLE_TO_HOST(header->width);
    _height = height = AX_SWAP_INT32_LITTLE_TO_HOST(header->height);

    // Move by size of header
    const int pixelOffset = sizeof(PVRv2TexHeader);
    uint8_t* pixelData    = data + pixelOffset;

    int dataOffset = 0, dataSize = 0;
    // Get ptr to where data starts..
    int dataLength = AX_SWAP_INT32_LITTLE_TO_HOST(header->dataLength);

    // Calculate the data size for each texture level and respect the minimum number of blocks
    while (dataOffset < dataLength)
    {
        switch (formatFlags)
        {
        case PVR2TexturePixelFormat::PVRTC2BPP_RGBA:
            if (!Configuration::getInstance()->supportsPVRTC())
            {
                AXLOG("axmol: Hardware PVR decoder not present. Using software decoder");
                _unpack                            = true;
                _mipmaps[_numberOfMipmaps].len     = width * height * 4;
                _mipmaps[_numberOfMipmaps].address = (uint8_t*)malloc(width * height * 4);
                PVRTDecompressPVRTC(pixelData + dataOffset, width, height, _mipmaps[_numberOfMipmaps].address, true);
                bpp = 2;
            }
            blockSize    = 8 * 4;  // Pixel by pixel block size for 2bpp
            widthBlocks  = width / 8;
            heightBlocks = height / 4;
            break;
        case PVR2TexturePixelFormat::PVRTC4BPP_RGBA:
            if (!Configuration::getInstance()->supportsPVRTC())
            {
                AXLOG("axmol: Hardware PVR decoder not present. Using software decoder");
                _unpack                            = true;
                _mipmaps[_numberOfMipmaps].len     = width * height * 4;
                _mipmaps[_numberOfMipmaps].address = (uint8_t*)malloc(width * height * 4);
                PVRTDecompressPVRTC(pixelData + dataOffset, width, height, _mipmaps[_numberOfMipmaps].address, false);
                bpp = 4;
            }
            blockSize    = 4 * 4;  // Pixel by pixel block size for 4bpp
            widthBlocks  = width / 4;
            heightBlocks = height / 4;
            break;
        case PVR2TexturePixelFormat::BGRA8888:
            if (!Configuration::getInstance()->supportsBGRA8888())
            {
                AXLOG("axmol: Image. BGRA8888 not supported on this device");
                return false;
            }
        default:
            blockSize    = 1;
            widthBlocks  = width;
            heightBlocks = height;
            break;
        }

        // Clamp to minimum number of blocks
        if (widthBlocks < 2)
        {
            widthBlocks = 2;
        }
        if (heightBlocks < 2)
        {
            heightBlocks = 2;
        }

        dataSize         = widthBlocks * heightBlocks * ((blockSize * bpp) / 8);
        int packetLength = (dataLength - dataOffset);
        packetLength     = packetLength > dataSize ? dataSize : packetLength;

        // Make record to the mipmaps array and increment counter
        if (!_unpack)
        {
            _mipmaps[_numberOfMipmaps].address = pixelData + dataOffset;
            _mipmaps[_numberOfMipmaps].len     = packetLength;
        }
        _numberOfMipmaps++;

        dataOffset += packetLength;

        // Update width and height to the next lower power of two
        width  = MAX(width >> 1, 1);
        height = MAX(height >> 1, 1);
    }

    if (!_unpack)
    {  // hardware decoder, hold data directly
        forwardPixels(data, dataLen, pixelOffset, ownData);
    }
    else
    {
        _data    = _mipmaps[0].address;
        _dataLen = _mipmaps[0].len;
    }

    return true;
}

bool Image::initWithPVRv3Data(uint8_t* data, ssize_t dataLen, bool ownData)
{
    if (static_cast<size_t>(dataLen) < sizeof(PVRv3TexHeader))
    {
        return false;
    }

    const PVRv3TexHeader* header = static_cast<const PVRv3TexHeader*>(static_cast<const void*>(data));

    // validate version
    if (AX_SWAP_INT32_BIG_TO_HOST(header->version) != 0x50565203)
    {
        AXLOG("axmol: WARNING: pvr file version mismatch");
        return false;
    }

    // parse pixel format
    PVR3TexturePixelFormat pixelFormat = static_cast<PVR3TexturePixelFormat>(header->pixelFormat);

    if (!testFormatForPvr3TCSupport(pixelFormat))
    {
        AXLOG(
            "axmol:WARNING: Unsupported PVR Pixel Format: 0x%016llX. Re-encode it with a OpenGL pixel format "
            "variant",
            static_cast<unsigned long long>(pixelFormat));
        return false;
    }

    if (v3_pixel_formathash.find(pixelFormat) == v3_pixel_formathash.end())
    {
        AXLOG(
            "axmol:WARNING: Unsupported PVR Pixel Format: 0x%016llX. Re-encode it with a OpenGL pixel format "
            "variant",
            static_cast<unsigned long long>(pixelFormat));
        return false;
    }

    auto finalPixelFormat = getDevicePVRPixelFormat(v3_pixel_formathash.at(pixelFormat));
    auto& info            = backend::PixelFormatUtils::getFormatDescriptor(finalPixelFormat);
    int bpp               = info.bpp;
    if (!info.bpp)
    {
        AXLOG(
            "axmol:WARNING: Unsupported PVR Pixel Format: 0x%016llX. Re-encode it with a OpenGL pixel format "
            "variant",
            static_cast<unsigned long long>(pixelFormat));
        return false;
    }

    _pixelFormat = finalPixelFormat;

    // flags
    int flags = AX_SWAP_INT32_LITTLE_TO_HOST(header->flags);

    // PVRv3 specifies premultiply alpha in a flag -- should always respect this in PVRv3 files
    if (flags & (unsigned int)PVR3TextureFlag::PremultipliedAlpha)
    {
        _hasPremultipliedAlpha = true;
    }

    // sizing
    int width  = AX_SWAP_INT32_LITTLE_TO_HOST(header->width);
    int height = AX_SWAP_INT32_LITTLE_TO_HOST(header->height);
    _width     = width;
    _height    = height;

    const int pixelOffset = (sizeof(PVRv3TexHeader) + header->metadataLength);
    uint8_t* pixelData    = data + pixelOffset;
    int pixelLen          = static_cast<int>(dataLen) - pixelOffset;

    int dataOffset = 0, dataSize = 0;
    int blockSize = 0, widthBlocks = 0, heightBlocks = 0;

    _numberOfMipmaps = header->numberOfMipmaps;
    AXASSERT(_numberOfMipmaps < MIPMAP_MAX,
             "Image: Maximum number of mimpaps reached. Increase the AX_MIPMAP_MAX value");

    for (int i = 0; i < _numberOfMipmaps; i++)
    {
        switch ((PVR3TexturePixelFormat)pixelFormat)
        {
        case PVR3TexturePixelFormat::PVRTC2BPP_RGB:
        case PVR3TexturePixelFormat::PVRTC2BPP_RGBA:
            if (!Configuration::getInstance()->supportsPVRTC())
            {
                AXLOG("axmol: Hardware PVR decoder not present. Using software decoder");
                _unpack             = true;
                _mipmaps[i].len     = width * height * 4;
                _mipmaps[i].address = (uint8_t*)malloc(width * height * 4);
                PVRTDecompressPVRTC(pixelData + dataOffset, width, height, _mipmaps[i].address, true);
                bpp = 2;
            }
            blockSize    = 8 * 4;  // Pixel by pixel block size for 2bpp
            widthBlocks  = width / 8;
            heightBlocks = height / 4;
            break;
        case PVR3TexturePixelFormat::PVRTC4BPP_RGB:
        case PVR3TexturePixelFormat::PVRTC4BPP_RGBA:
            if (!Configuration::getInstance()->supportsPVRTC())
            {
                AXLOG("axmol: Hardware PVR decoder not present. Using software decoder");
                _unpack             = true;
                _mipmaps[i].len     = width * height * 4;
                _mipmaps[i].address = (uint8_t*)malloc(width * height * 4);
                PVRTDecompressPVRTC(pixelData + dataOffset, width, height, _mipmaps[i].address, false);
                bpp = 4;
            }
            blockSize    = 4 * 4;  // Pixel by pixel block size for 4bpp
            widthBlocks  = width / 4;
            heightBlocks = height / 4;
            break;
        case PVR3TexturePixelFormat::ETC1:
            if (!Configuration::getInstance()->supportsETC1())
            {
                AXLOG("axmol: Hardware ETC1 decoder not present. Using software decoder");
                const int bytePerPixel = 4;
                _unpack                = true;
                _mipmaps[i].len        = width * height * bytePerPixel;
                _mipmaps[i].address    = (uint8_t*)malloc(width * height * bytePerPixel);
                if (etc2_decode_image(ETC2_RGB_NO_MIPMAPS, pixelData + dataOffset,
                                      static_cast<etc1_byte*>(_mipmaps[i].address), width, height) != 0)
                {
                    return false;
                }
            }
            blockSize    = 4 * 4;  // Pixel by pixel block size for 4bpp
            widthBlocks  = width / 4;
            heightBlocks = height / 4;
            break;
        case PVR3TexturePixelFormat::BGRA8888:
            if (!Configuration::getInstance()->supportsBGRA8888())
            {
                AXLOG("axmol: Image. BGRA8888 not supported on this device");
                return false;
            }
        default:
            blockSize    = 1;
            widthBlocks  = width;
            heightBlocks = height;
            break;
        }

        // Clamp to minimum number of blocks
        if (widthBlocks < 2)
        {
            widthBlocks = 2;
        }
        if (heightBlocks < 2)
        {
            heightBlocks = 2;
        }

        dataSize          = widthBlocks * heightBlocks * ((blockSize * bpp) / 8);
        auto packetLength = pixelLen - dataOffset;
        packetLength      = packetLength > dataSize ? dataSize : packetLength;

        if (!_unpack)
        {
            _mipmaps[i].address = pixelData + dataOffset;
            _mipmaps[i].len     = static_cast<int>(packetLength);
        }

        dataOffset += packetLength;
        AXASSERT(dataOffset <= pixelLen, "Image: Invalid length");

        width  = MAX(width >> 1, 1);
        height = MAX(height >> 1, 1);
    }

    if (!_unpack)
    {
        forwardPixels(data, dataLen, pixelOffset, ownData);
    }
    else
    {
        _data    = _mipmaps[0].address;
        _dataLen = _mipmaps[0].len;
    }

    return true;
}

bool Image::initWithETCData(uint8_t* data, ssize_t dataLen, bool ownData)
{
    const etc1_byte* header = static_cast<const etc1_byte*>(data);
    uint32_t pixelOffset;
    // check the data
    if (etc1_pkm_is_valid(header))
    {
        _width  = etc1_pkm_get_width(header);
        _height = etc1_pkm_get_height(header);

        if (0 == _width || 0 == _height)
            return false;
        pixelOffset = ETC_PKM_HEADER_SIZE;
    }
    else
    {  // we can safe trait as KTX v1 header
        auto header = (KTXv1Header*)data;
        _width      = header->pixelWidth;
        _height     = header->pixelHeight;

        if (0 == _width || 0 == _height)
            return false;
        pixelOffset = KTX_V1_HEADER_SIZE + header->bytesOfKeyValueData + 4;
    }

    // GL_ETC1_RGB8_OES is not available in any desktop GL extension but the compression
    // format is forwards compatible so just use the ETC2 format.
    backend::PixelFormat compressedFormat;
    if (Configuration::getInstance()->supportsETC1())
        compressedFormat = backend::PixelFormat::ETC1;
    else if (Configuration::getInstance()->supportsETC2())
        compressedFormat = backend::PixelFormat::ETC2_RGB;
    else
        compressedFormat = backend::PixelFormat::NONE;

    if (compressedFormat != backend::PixelFormat::NONE)
    {
        _pixelFormat = compressedFormat;
        forwardPixels(data, dataLen, pixelOffset, ownData);
        return true;
    }
    else
    {
        AXLOG("axmol: Hardware ETC1 decoder not present. Using software decoder");

        _dataLen = _width * _height * 4;
        _data    = static_cast<uint8_t*>(malloc(_dataLen));
        if (etc2_decode_image(ETC2_RGB_NO_MIPMAPS, static_cast<const uint8_t*>(data) + pixelOffset,
                              static_cast<etc2_byte*>(_data), _width, _height) == 0)
        {  // if it is not gles or device do not support ETC1, decode texture by software
           // directly decode ETC1_RGB to RGBA8888
            _pixelFormat = backend::PixelFormat::RGBA8;
            return true;
        }

        // software decode fail, release pixels data
        AX_SAFE_FREE(_data);
        _dataLen = 0;
        return false;
    }
}

bool Image::initWithETC2Data(uint8_t* data, ssize_t dataLen, bool ownData)
{
    const etc2_byte* header = static_cast<const etc2_byte*>(data);

    do
    {
        uint32_t format, pixelOffset;
        // check the data
        if (etc2_pkm_is_valid(header))
        {

            _width  = etc2_pkm_get_width(header);
            _height = etc2_pkm_get_height(header);

            if (0 == _width || 0 == _height)
                break;

            format      = etc2_pkm_get_format(header);
            pixelOffset = ETC2_PKM_HEADER_SIZE;
        }
        else
        {  // we can safe trait as KTX v1 header
            auto header = (KTXv1Header*)data;
            _width      = header->pixelWidth;
            _height     = header->pixelHeight;

            if (0 == _width || 0 == _height)
                break;

            format      = header->glInternalFormat == KTXv1Header::InternalFormat::ETC2_RGBA8 ? ETC2_RGBA_NO_MIPMAPS
                                                                                              : ETC2_RGB_NO_MIPMAPS;
            pixelOffset = KTX_V1_HEADER_SIZE + header->bytesOfKeyValueData + 4;
        }

        // We only support ETC2_RGBA_NO_MIPMAPS and ETC2_RGB_NO_MIPMAPS
        assert(format == ETC2_RGBA_NO_MIPMAPS || format == ETC2_RGB_NO_MIPMAPS);

        if (Configuration::getInstance()->supportsETC2())
        {
            _pixelFormat =
                format == ETC2_RGBA_NO_MIPMAPS ? backend::PixelFormat::ETC2_RGBA : backend::PixelFormat::ETC2_RGB;

            forwardPixels(data, dataLen, pixelOffset, ownData);
        }
        else
        {
            AXLOG("axmol: Hardware ETC2 decoder not present. Using software decoder");

            // if device do not support ETC2, decode texture by software
            // etc2_decode_image always decode to RGBA8888
            _dataLen = _width * _height * 4;
            _data    = static_cast<uint8_t*>(malloc(_dataLen));
            if (UTILS_UNLIKELY(etc2_decode_image(format, static_cast<const uint8_t*>(data) + pixelOffset,
                                                 static_cast<etc2_byte*>(_data), _width, _height) != 0))
            {
                // software decode fail, release pixels data
                AX_SAFE_FREE(_data);
                _dataLen = 0;
                break;
            }
            _pixelFormat = backend::PixelFormat::RGBA8;
        }

        _hasPremultipliedAlpha = isCompressedImageHavePMA(CompressedImagePMAFlag::ETC2);

        return true;
    } while (false);

    return false;
}

bool Image::initWithASTCData(uint8_t* data, ssize_t dataLen, bool ownData)
{
    astc_header* hdr = (astc_header*)data;

    do
    {
        // Ensure these are not zero to avoid div by zero
        unsigned int block_x = (std::max)((unsigned int)hdr->block_x, 1u);
        unsigned int block_y = (std::max)((unsigned int)hdr->block_y, 1u);
        // unsigned int block_z = std::max((unsigned int) hdr->block_z, 1u);

        unsigned int dim_x = astc_unpack_bytes(hdr->dim_x[0], hdr->dim_x[1], hdr->dim_x[2], 0);
        unsigned int dim_y = astc_unpack_bytes(hdr->dim_y[0], hdr->dim_y[1], hdr->dim_y[2], 0);
        // unsigned int dim_z = astc_unpack_bytes(hdr->dim_z[0], hdr->dim_z[1], hdr->dim_z[2], 0);

        if (dim_x == 0 || dim_y == 0)
            break;

        _width  = dim_x;
        _height = dim_y;

        if (block_x < 4 || block_y < 4)
        {
            AXLOG("axmol: The ASTC block with and height should be >= 4");
            break;
        }

        if (Configuration::getInstance()->supportsASTC())
        {
            if (block_x == 4 && block_y == 4)
            {
                _pixelFormat = backend::PixelFormat::ASTC4x4;
            }
            else if (block_x == 5 && block_y == 5)
            {
                _pixelFormat = backend::PixelFormat::ASTC5x5;
            }
            else if (block_x == 6 && block_y == 6)
            {
                _pixelFormat = backend::PixelFormat::ASTC6x6;
            }
            else if (block_x == 8 && block_y == 5)
            {
                _pixelFormat = backend::PixelFormat::ASTC8x5;
            }
            else if (block_x == 8 && block_y == 6)
            {
                _pixelFormat = backend::PixelFormat::ASTC8x6;
            }
            else if (block_x == 8 && block_y == 8)
            {
                _pixelFormat = backend::PixelFormat::ASTC8x8;
            }
            else if (block_x == 10 && block_y == 5)
            {
                _pixelFormat = backend::PixelFormat::ASTC10x5;
            }

            forwardPixels(data, dataLen, ASTC_HEAD_SIZE, ownData);
        }
        else
        {
            AXLOG("axmol: Hardware ASTC decoder not present. Using software decoder");

            _dataLen = _width * _height * 4;
            _data    = static_cast<uint8_t*>(malloc(_dataLen));
            if (UTILS_UNLIKELY(astc_decompress_image(static_cast<const uint8_t*>(data) + ASTC_HEAD_SIZE,
                                                     static_cast<uint32_t>(dataLen) - ASTC_HEAD_SIZE, _data, _width,
                                                     _height, block_x, block_y) != 0))
            {
                AX_SAFE_FREE(_data);
                _dataLen = 0;
                break;
            }

            _pixelFormat = backend::PixelFormat::RGBA8;
        }

        _hasPremultipliedAlpha = isCompressedImageHavePMA(CompressedImagePMAFlag::ASTC);

        return true;
    } while (false);

    return false;
}

bool Image::initWithS3TCData(uint8_t* data, ssize_t dataLen, bool ownData)
{
    const uint32_t FOURCC_DXT1 = makeFourCC('D', 'X', 'T', '1');
    const uint32_t FOURCC_DXT3 = makeFourCC('D', 'X', 'T', '3');
    const uint32_t FOURCC_DXT5 = makeFourCC('D', 'X', 'T', '5');

    /* load the .dds file */

    S3TCTexHeader* header = (S3TCTexHeader*)data;
    _width                = header->ddsd.width;
    _height               = header->ddsd.height;
    _numberOfMipmaps      = MAX(
        1,
        header->ddsd.DUMMYUNIONNAMEN2
            .mipMapCount);  // if dds header reports 0 mipmaps, set to 1 to force correct software decoding (if needed).
    _dataLen      = 0;
    int blockSize = (FOURCC_DXT1 == header->ddsd.DUMMYUNIONNAMEN4.ddpfPixelFormat.fourCC) ? 8 : 16;

    /* calculate the dataLen */

    int width  = _width;
    int height = _height;

    const int pixelOffset = sizeof(S3TCTexHeader);
    uint8_t* pixelData    = data + pixelOffset;

    bool hardware = Configuration::getInstance()->supportsS3TC();
    /* if hardware supports s3tc, set pixelformat before loading mipmaps, to support non-mipmapped textures  */
    if (hardware)
    {  // decode texture through hardware

        if (FOURCC_DXT1 == header->ddsd.DUMMYUNIONNAMEN4.ddpfPixelFormat.fourCC)
        {
            _pixelFormat = backend::PixelFormat::S3TC_DXT1;
        }
        else if (FOURCC_DXT3 == header->ddsd.DUMMYUNIONNAMEN4.ddpfPixelFormat.fourCC)
        {
            _pixelFormat = backend::PixelFormat::S3TC_DXT3;
        }
        else if (FOURCC_DXT5 == header->ddsd.DUMMYUNIONNAMEN4.ddpfPixelFormat.fourCC)
        {
            _pixelFormat = backend::PixelFormat::S3TC_DXT5;
        }
    }
    else
    {  // will software decode
        _pixelFormat = backend::PixelFormat::RGBA8;

        // prepare data for software decompress
        for (int i = 0; i < _numberOfMipmaps && (width || height); ++i)
        {
            if (width == 0)
                width = 1;
            if (height == 0)
                height = 1;

            _dataLen += (height * width * 4);

            width >>= 1;
            height >>= 1;
        }
        _data = static_cast<uint8_t*>(malloc(_dataLen));
    }

    /* load the mipmaps */
    int encodeOffset = 0;
    int decodeOffset = 0;
    width            = _width;
    height           = _height;

    for (int i = 0; i < _numberOfMipmaps && (width || height); ++i)
    {
        if (width == 0)
            width = 1;
        if (height == 0)
            height = 1;

        int size = ((width + 3) / 4) * ((height + 3) / 4) * blockSize;

        if (Configuration::getInstance()->supportsS3TC())
        {  // decode texture through hardware
            _mipmaps[i].address = (uint8_t*)pixelData + encodeOffset;
            _mipmaps[i].len     = size;
        }
        else
        {  // if it is not gles or device do not support S3TC, decode texture by software

            AXLOG("axmol: Hardware S3TC decoder not present. Using software decoder");

            int bytePerPixel    = 4;
            unsigned int stride = width * bytePerPixel;

            auto decodeImageData = axstd::make_unique_for_overwrite<uint8_t[]>(stride * height);
            if (FOURCC_DXT1 == header->ddsd.DUMMYUNIONNAMEN4.ddpfPixelFormat.fourCC)
            {
                s3tc_decode(pixelData + encodeOffset, &decodeImageData[0], width, height, S3TCDecodeFlag::DXT1);
            }
            else if (FOURCC_DXT3 == header->ddsd.DUMMYUNIONNAMEN4.ddpfPixelFormat.fourCC)
            {
                s3tc_decode(pixelData + encodeOffset, &decodeImageData[0], width, height, S3TCDecodeFlag::DXT3);
            }
            else if (FOURCC_DXT5 == header->ddsd.DUMMYUNIONNAMEN4.ddpfPixelFormat.fourCC)
            {
                s3tc_decode(pixelData + encodeOffset, &decodeImageData[0], width, height, S3TCDecodeFlag::DXT5);
            }

            _mipmaps[i].address = (uint8_t*)_data + decodeOffset;
            _mipmaps[i].len     = (stride * height);
            memcpy((void*)_mipmaps[i].address, (void*)&decodeImageData[0], _mipmaps[i].len);
            decodeOffset += stride * height;
        }

        encodeOffset += size;
        width >>= 1;
        height >>= 1;
    }

    /* end load the mipmaps */

    if (hardware)
    {
        forwardPixels(data, dataLen, pixelOffset, ownData);
    }

    return true;
}

bool Image::initWithATITCData(uint8_t* data, ssize_t dataLen, bool ownData)
{
    /* load the .ktx file */
    KTXv1Header* header = (KTXv1Header*)data;
    _width              = header->pixelWidth;
    _height             = header->pixelHeight;
    _numberOfMipmaps    = header->numberOfMipmapLevels;

    int blockSize = 0;
    switch (header->glInternalFormat)
    {
    case KTXv1Header::InternalFormat::ATC_RGB_AMD:
        blockSize = 8;
        break;
    case KTXv1Header::InternalFormat::ATC_RGBA_EXPLICIT_ALPHA_AMD:
        blockSize = 16;
        break;
    case KTXv1Header::InternalFormat::ATC_RGBA_INTERPOLATED_ALPHA_AMD:
        blockSize = 16;
        break;
    default:
        break;
    }

    /* pixelData point to the compressed data address */
    int pixelOffset    = KTX_V1_HEADER_SIZE + header->bytesOfKeyValueData + 4;
    uint8_t* pixelData = (uint8_t*)data + pixelOffset;

    /* calculate the dataLen */
    int width  = _width;
    int height = _height;

    bool hardware = Configuration::getInstance()->supportsATITC();
    if (hardware)  // compressed data length
    {
        AXLOG("this is atitc H decode");

        switch (header->glInternalFormat)
        {
        case KTXv1Header::InternalFormat::ATC_RGB_AMD:
            _pixelFormat = backend::PixelFormat::ATC_RGB;
            break;
        case KTXv1Header::InternalFormat::ATC_RGBA_EXPLICIT_ALPHA_AMD:
            _pixelFormat = backend::PixelFormat::ATC_EXPLICIT_ALPHA;
            break;
        case KTXv1Header::InternalFormat::ATC_RGBA_INTERPOLATED_ALPHA_AMD:
            _pixelFormat = backend::PixelFormat::ATC_INTERPOLATED_ALPHA;
            break;
        default:
            break;
        }
    }
    else  // decompressed data length
    {     /* if it is not gles or device do not support ATITC, decode texture by software */

        AXLOG("axmol: Hardware ATITC decoder not present. Using software decoder");

        _pixelFormat = backend::PixelFormat::RGBA8;

        for (int i = 0; i < _numberOfMipmaps && (width || height); ++i)
        {
            if (width == 0)
                width = 1;
            if (height == 0)
                height = 1;

            _dataLen += (height * width * 4);

            width >>= 1;
            height >>= 1;
        }
        _data = static_cast<uint8_t*>(malloc(_dataLen));
    }

    /* load the mipmaps */
    int encodeOffset = 0;
    int decodeOffset = 0;
    width            = _width;
    height           = _height;

    for (int i = 0; i < _numberOfMipmaps && (width || height); ++i)
    {
        if (width == 0)
            width = 1;
        if (height == 0)
            height = 1;

        int size = ((width + 3) / 4) * ((height + 3) / 4) * blockSize;

        if (hardware)
        {
            /* decode texture through hardware */
            _mipmaps[i].address = (uint8_t*)pixelData + encodeOffset;
            _mipmaps[i].len     = size;
        }
        else
        {
            int bytePerPixel    = 4;
            unsigned int stride = width * bytePerPixel;

            std::vector<uint8_t> decodeImageData(stride * height);
            switch (header->glInternalFormat)
            {
            case KTXv1Header::InternalFormat::ATC_RGB_AMD:
                atitc_decode(pixelData + encodeOffset, &decodeImageData[0], width, height, ATITCDecodeFlag::ATC_RGB);
                break;
            case KTXv1Header::InternalFormat::ATC_RGBA_EXPLICIT_ALPHA_AMD:
                atitc_decode(pixelData + encodeOffset, &decodeImageData[0], width, height,
                             ATITCDecodeFlag::ATC_EXPLICIT_ALPHA);
                break;
            case KTXv1Header::InternalFormat::ATC_RGBA_INTERPOLATED_ALPHA_AMD:
                atitc_decode(pixelData + encodeOffset, &decodeImageData[0], width, height,
                             ATITCDecodeFlag::ATC_INTERPOLATED_ALPHA);
                break;
            default:
                break;
            }

            _mipmaps[i].address = (uint8_t*)_data + decodeOffset;
            _mipmaps[i].len     = (stride * height);
            memcpy((void*)_mipmaps[i].address, (void*)&decodeImageData[0], _mipmaps[i].len);
            decodeOffset += stride * height;
        }

        encodeOffset += (size + 4);
        width >>= 1;
        height >>= 1;
    }
    /* end load the mipmaps */

    if (hardware)
    {
        forwardPixels(data, dataLen, pixelOffset, ownData);
    }

    return true;
}

bool Image::initWithPVRData(uint8_t* data, ssize_t dataLen, bool ownData)
{
    return initWithPVRv2Data(data, dataLen, ownData) || initWithPVRv3Data(data, dataLen, ownData);
}

void Image::forwardPixels(uint8_t* data, ssize_t dataLen, int offset, bool ownData)
{
    if (ownData)
    {
        _data    = data;
        _dataLen = dataLen;
        _offset  = offset;
    }
    else
    {
        _dataLen = dataLen - offset;
        _data    = (uint8_t*)malloc(_dataLen);
        memcpy(_data, data + offset, _dataLen);
    }
}

#if (AX_TARGET_PLATFORM != AX_PLATFORM_IOS)
bool Image::saveToFile(std::string_view filename, bool isToRGB)
{
    // only support for backend::PixelFormat::RGB8 or backend::PixelFormat::RGBA8 uncompressed data
    if (isCompressed() || (_pixelFormat != backend::PixelFormat::RGB8 && _pixelFormat != backend::PixelFormat::RGBA8))
    {
        AXLOG(
            "axmol:Image: saveToFile is only support for backend::PixelFormat::RGB8 or backend::PixelFormat::RGBA8 "
            "uncompressed data for now");
        return false;
    }

    std::string fileExtension = FileUtils::getInstance()->getFileExtension(filename);

    if (fileExtension == ".png")
    {
        return saveImageToPNG(filename, isToRGB);
    }
    else if (fileExtension == ".jpg")
    {
        return saveImageToJPG(filename);
    }
    else
    {
        AXLOG("axmol: Image: saveToFile no support file extension(only .png or .jpg) for file: %s", filename.data());
        return false;
    }
}
#endif

bool Image::saveImageToPNG(std::string_view filePath, bool isToRGB)
{
#if AX_USE_WIC
    return encodeWithWIC(filePath, isToRGB, GUID_ContainerFormatPng);
#elif AX_USE_PNG
    bool ret = false;
    do
    {
        png_structp png_ptr;
        png_infop info_ptr;
        png_bytep* row_pointers;

        auto outStream = FileUtils::getInstance()->openFileStream(filePath, IFileStream::Mode::WRITE);

        AX_BREAK_IF(nullptr == outStream);

        png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);

        if (nullptr == png_ptr)
        {
            outStream.reset();
            break;
        }

        info_ptr = png_create_info_struct(png_ptr);
        if (nullptr == info_ptr)
        {
            outStream.reset();
            png_destroy_write_struct(&png_ptr, nullptr);
            break;
        }
        if (setjmp(png_jmpbuf(png_ptr)))
        {
            outStream.reset();
            png_destroy_write_struct(&png_ptr, &info_ptr);
            break;
        }

        // png_init_io(png_ptr, outStream);
        png_set_write_fn(png_ptr, outStream.get(), pngWriteCallback, nullptr);

        if (!isToRGB && hasAlpha())
        {
            png_set_IHDR(png_ptr, info_ptr, _width, _height, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
                         PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
        }
        else
        {
            png_set_IHDR(png_ptr, info_ptr, _width, _height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
                         PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
        }

        png_write_info(png_ptr, info_ptr);

        png_set_packing(png_ptr);

        row_pointers = (png_bytep*)malloc(_height * sizeof(png_bytep));
        if (row_pointers == nullptr)
        {
            outStream.reset();
            png_destroy_write_struct(&png_ptr, &info_ptr);
            break;
        }

        if (!hasAlpha())
        {
            for (int i = 0; i < (int)_height; i++)
            {
                row_pointers[i] = (png_bytep)_data + i * _width * 3;
            }

            png_write_image(png_ptr, row_pointers);

            free(row_pointers);
            row_pointers = nullptr;
        }
        else
        {
            if (isToRGB)
            {
                uint8_t* tempData = static_cast<uint8_t*>(malloc(_width * _height * 3));
                if (nullptr == tempData)
                {
                    outStream.reset();
                    png_destroy_write_struct(&png_ptr, &info_ptr);

                    free(row_pointers);
                    row_pointers = nullptr;
                    break;
                }

                for (int i = 0; i < _height; ++i)
                {
                    for (int j = 0; j < _width; ++j)
                    {
                        tempData[(i * _width + j) * 3]     = _data[(i * _width + j) * 4];
                        tempData[(i * _width + j) * 3 + 1] = _data[(i * _width + j) * 4 + 1];
                        tempData[(i * _width + j) * 3 + 2] = _data[(i * _width + j) * 4 + 2];
                    }
                }

                for (int i = 0; i < (int)_height; i++)
                {
                    row_pointers[i] = (png_bytep)tempData + i * _width * 3;
                }

                png_write_image(png_ptr, row_pointers);

                free(row_pointers);
                row_pointers = nullptr;

                if (tempData != nullptr)
                {
                    free(tempData);
                }
            }
            else
            {
                for (int i = 0; i < (int)_height; i++)
                {
                    row_pointers[i] = (png_bytep)_data + i * _width * 4;
                }

                png_write_image(png_ptr, row_pointers);

                free(row_pointers);
                row_pointers = nullptr;
            }
        }

        png_write_end(png_ptr, info_ptr);

        png_destroy_write_struct(&png_ptr, &info_ptr);

        outStream.reset();

        ret = true;
    } while (0);
    return ret;
#else
    AXLOG("png is not enabled, please enable it in ccConfig.h");
    return false;
#endif  // AX_USE_PNG
}

bool Image::saveImageToJPG(std::string_view filePath)
{
#if AX_USE_WIC
    return encodeWithWIC(filePath, false, GUID_ContainerFormatJpeg);
#elif AX_USE_JPEG
    bool ret = false;
    do
    {
        struct jpeg_compress_struct cinfo;
        struct jpeg_error_mgr jerr;
        JSAMPROW row_pointer[1];             /* pointer to JSAMPLE row[s] */
        int row_stride;                      /* physical row width in image buffer */

        cinfo.err = jpeg_std_error(&jerr);
        /* Now we can initialize the JPEG compression object. */
        jpeg_create_compress(&cinfo);

        auto outfile = FileUtils::getInstance()->openFileStream(filePath, IFileStream::Mode::WRITE);
        AX_BREAK_IF(nullptr == outfile);

        unsigned char* outputBuffer = nullptr;
        unsigned long outputSize    = 0;
        jpeg_mem_dest(&cinfo, &outputBuffer, &outputSize);

        cinfo.image_width      = _width; /* image width and height, in pixels */
        cinfo.image_height     = _height;
        cinfo.input_components = 3;       /* # of color components per pixel */
        cinfo.in_color_space   = JCS_RGB; /* colorspace of input image */

        jpeg_set_defaults(&cinfo);
        jpeg_set_quality(&cinfo, 90, TRUE);

        jpeg_start_compress(&cinfo, TRUE);

        row_stride = _width * 3; /* JSAMPLEs per row in image_buffer */

        if (hasAlpha())
        {
            uint8_t* tempData = static_cast<uint8_t*>(malloc(_width * _height * 3));
            if (nullptr == tempData)
            {
                jpeg_finish_compress(&cinfo);
                jpeg_destroy_compress(&cinfo);

                if (outputBuffer)
                {
                    free(outputBuffer);
                    outputBuffer = nullptr;
                }
                break;
            }

            for (int i = 0; i < _height; ++i)
            {
                for (int j = 0; j < _width; ++j)

                {
                    tempData[(i * _width + j) * 3]     = _data[(i * _width + j) * 4];
                    tempData[(i * _width + j) * 3 + 1] = _data[(i * _width + j) * 4 + 1];
                    tempData[(i * _width + j) * 3 + 2] = _data[(i * _width + j) * 4 + 2];
                }
            }

            while (cinfo.next_scanline < cinfo.image_height)
            {
                row_pointer[0] = &tempData[cinfo.next_scanline * row_stride];
                (void)jpeg_write_scanlines(&cinfo, row_pointer, 1);
            }

            if (tempData != nullptr)
            {
                free(tempData);
            }
        }
        else
        {
            while (cinfo.next_scanline < cinfo.image_height)
            {
                row_pointer[0] = &_data[cinfo.next_scanline * row_stride];
                (void)jpeg_write_scanlines(&cinfo, row_pointer, 1);
            }
        }

        jpeg_finish_compress(&cinfo);

        outfile->write(outputBuffer, static_cast<unsigned int>(outputSize));

        if (outputBuffer)
        {
            free(outputBuffer);
            outputBuffer = nullptr;
        }

        jpeg_destroy_compress(&cinfo);

        ret = true;
    } while (0);
    return ret;
#else
    AXLOG("jpeg is not enabled, please enable it in ccConfig.h");
    return false;
#endif  // AX_USE_JPEG
}

void Image::premultiplyAlpha()
{
#if AX_ENABLE_PREMULTIPLIED_ALPHA
    AXASSERT(_pixelFormat == backend::PixelFormat::RGBA8, "The pixel format should be RGBA8888!");

    unsigned int* fourBytes = (unsigned int*)_data;
    for (int i = 0; i < _width * _height; i++)
    {
        uint8_t* p   = _data + i * 4;
        fourBytes[i] = AX_RGB_PREMULTIPLY_ALPHA(p[0], p[1], p[2], p[3]);
    }

    _hasPremultipliedAlpha = true;
#else
    _hasPremultipliedAlpha = false;
#endif
}

static inline uint8_t clamp(int x)
{
    return (uint8_t)(x >= 0 ? (x < 255 ? x : 255) : 0);
}

void Image::reversePremultipliedAlpha()
{
    AXASSERT(_pixelFormat == backend::PixelFormat::RGBA8, "The pixel format should be RGBA8888!");

    unsigned int* fourBytes = (unsigned int*)_data;
    for (int i = 0; i < _width * _height; i++)
    {
        uint8_t* p = _data + i * 4;
        if (p[3] > 0)
        {
            fourBytes[i] = clamp(int(std::ceil((p[0] * 255.0f) / p[3]))) |
                           clamp(int(std::ceil((p[1] * 255.0f) / p[3]))) << 8 |
                           clamp(int(std::ceil((p[2] * 255.0f) / p[3]))) << 16 | p[3] << 24;
        }
    }

    _hasPremultipliedAlpha = false;
}

NS_AX_END
